package com.shiro.filter;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import com.common.ActionResult;
import com.common.Const;
import com.common.LoginErrorTypeEnum;
import com.shiro.token.StatelessToken;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.ObjectMapper;


/**
 * 无状态应用filter
 * @author Eason
 *
 */
public class StatelessAuthcFilter extends AccessControlFilter {
	
	private static Logger logger = LoggerFactory.getLogger(StatelessAuthcFilter.class);

	/**
	 * 重写isAccessAllowed,默认不允许通过,然后进入onAccessDenied方法实现stateless的登录
	 */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
    	logger.info("[stateless:isAccessAllowed]");
    	// 这里无法判断是什么原因造成的onAccessDenied,所以isAllowed统一返回false交由这里处理
    	return false;
    }

    /**
     * 重写父类方法获得mappedValue,即stateless[admin,customer]中的admin,customer
     * 可以自定义该fitler的与、或、非权限
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception{
    	logger.info("[stateless:onAccessDenied(mappedValue)]");
    	Subject subject = getSubject(request, response);
        String[] rolesArray = (String[]) mappedValue;

        if(rolesArray!=null && rolesArray.length>0){
        	Set<String> roles = CollectionUtils.asSet(rolesArray);
            if(!subject.hasAllRoles(roles)){
            	logger.info("未通过");
            	// 返回登录失败,原因：未授权
            	onLoginFail(response, LoginErrorTypeEnum.UNAUTHORIZED);
            	return false;
            }
        }
    	return onAccessDenied(request,response);
    }
     
    
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
    
    	logger.info("[stateless:onAccessDenied()]");
    	HttpServletRequest httpRequest = (HttpServletRequest) request;
    	logger.info( " url is: " + httpRequest.getRequestURI() + " queryString is " + httpRequest.getQueryString());
    	
    	// 1、获取客户端传递的:sequence token params 以及由三者生成的digest(摘要)
    	// 客户端生成的消息摘要
        String clientDigest = request.getParameter(Const.PARAM_DIGEST);
    	//String clientDigest = httpRequest.getHeader(Consts.PARAM_DIGEST);

        // 客户端传入的用户身份token
        String username = request.getParameter(Const.PARAM_USERNAME);
    	//String username = httpRequest.getHeader(Consts.PARAM_USERNAME);
        
        // 用户传入的sequence(这里是时间戳) (1)作为序号区分请求(2)用于判断该请求是否在有效时间范围内,防止"重放攻击"
        String sequence = request.getParameter(Const.PARAM_SEQUENCE);
    	//String sequence = httpRequest.getHeader(Consts.PARAM_SEQUENCE);
        
        // 获得时间差
        long dvalue = System.currentTimeMillis() - Long.parseLong(sequence);
        // 判断超过60秒即为重放攻击
        if(dvalue/1000>60){
        	// 返回登录失败,原因：超时
        	onLoginFail(response,LoginErrorTypeEnum.TIMEOUT);
        	return false;
        }
        	
        logger.info("clientDigest: " + clientDigest +" userName: " + username +" sequence: " + sequence);
    	
        // 如果摘要、token、sequence 有一个为空,则返回登录失败
        if (StringUtils.isBlank(clientDigest) || StringUtils.isBlank(username) || StringUtils.isBlank(sequence)){
        	// 返回登录失败,原因：凭证无效
            onLoginFail(response,LoginErrorTypeEnum.INVALID); 
            return false;
        }
        
        // 2、获取客户端请求的参数列表,将摘要remove,以便重新生成摘要后与传递过来的摘要对比
        Map<String, String[]> params = new HashMap<String, String[]>(request.getParameterMap());
        params.remove(Const.PARAM_DIGEST);
        //params.remove(Const.PARAM_USERNAME);
        //params.remove(Const.PARAM_SEQUENCE);
        //params.remove(Consts.PARAM_FILE);
        
        // 3、生成无状态Token
        StatelessToken token = new StatelessToken(username, sequence, params, clientDigest);

        try {
            // 4、委托给Realm进行登录
            Subject subject = getSubject(request, response);
            subject.logout();
            subject.login(token);
           
        } catch (Exception e) {
        	// 5、登录失败
        	logger.info("[stateless:onAccessDenied]登录失败,错误信息如下:");
            e.printStackTrace();
            // 返回登录失败,原因：凭证无效
            onLoginFail(response,LoginErrorTypeEnum.INVALID); 
            return false;
        }
        return true;
    }

    // 登录失败时,返回json,提示用户没有权限访问
    private void onLoginFail(ServletResponse response,LoginErrorTypeEnum loginError) throws IOException {
        logger.info("[stateless:onLoginFail]");
	    try {
	    	// 响应请求,返回json字符串
	    	response.setContentType("application/json");
	    	response.setCharacterEncoding("UTF-8");
	        PrintWriter out = response.getWriter();
	    	ActionResult actionResult = new ActionResult("", Const._INT_FALSE, loginError.getName(), loginError.getValue(), null);
	        ObjectMapper mapper = new ObjectMapper();
	        String json = mapper.writeValueAsString(actionResult);
	        
	        out.println(json);
	        
	        out.flush();
	        out.close();
	    } catch (IOException e1) {
	    	// TODO Auto-generated catch block
	    	e1.printStackTrace();
	    } 
    }
}